/*
 * Copyright (c) 2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.libermundi.theorcs.core.model.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import org.libermundi.theorcs.core.model.Node;
import org.libermundi.theorcs.core.model.NodeConstrain;
import org.libermundi.theorcs.core.model.NodeConstrain.Mode;
import org.libermundi.theorcs.core.model.NodeData;

import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;

public class NodeImpl<T extends NodeData> implements Node<T> {
	private static final long serialVersionUID = 8368889340915517919L;
	private String _id;
	private String _parentId;
	private T _data;
	private List<Node<T>> _children;
	private List<NodeConstrain> _constrains;
	private boolean _displayIfEmpty;
	private NodeConstrain.Mode _constrainMode = NodeConstrain.Mode.ALL;
	
	/**
	 * Default constructor.
	 */
	public NodeImpl() {
	    this(UUID.randomUUID().toString(), null);
	}

	/**
	 * Convenience constructor to create a Node<T> with an instance of T.
	 * @param data an instance of T.
	 */
	public NodeImpl(T data) {
		this(UUID.randomUUID().toString(),data);
	}
	
	public NodeImpl(String id){
		this(id,null);
	}
	
	public NodeImpl(String id, T data){
		this._id = id;
	    this._children = Lists.newArrayList();
	    this._constrains = Lists.newArrayList();
	    this._displayIfEmpty = Boolean.TRUE;
		setData(data);
	}	
	 
	/*
	 * (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#getChildren()
	 */
	@Override
	public List<Node<T>> getChildren() {
	    return this._children;
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#setChildren(java.util.List)
	 */
	@Override
	public void setChildren(List<Node<T>> children) {
	    this._children = children;
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#getNumberOfChildren()
	 */
	@Override
	public int getNumberOfChildren() {
	    if (_children == null) {
	        return 0;
	    }
	    return _children.size();
	}
	 
	/*
	 * (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#addChild(org.libermundi.theorcs.core.model.Node)
	 */
	@Override
	public void addChild(Node<T> child) {
	    if (_children == null) {
	        _children = new ArrayList<>();
	    }
	    _children.add(child);
	}
	 
	/*
	 * (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#insertChildAt(int, org.libermundi.theorcs.core.model.Node)
	 */
	@Override
	public void insertChildAt(int index, Node<T> child) throws IndexOutOfBoundsException {
	    if (index == getNumberOfChildren()) {
	        // this is really an append
	        addChild(child);
	        return;
	    }
	    _children.get(index); //just to throw the exception, and stop here
	    _children.add(index, child);
	}
	 
	/*
	 * (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#removeChildAt(int)
	 */
	@Override
	public void removeChildAt(int index) throws IndexOutOfBoundsException {
	    _children.remove(index);
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#getData()
	 */
	@Override
	public T getData() {
	    return this._data;
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#setData(java.lang.Object)
	 */
	@Override
	public void setData(T data) {
	    this._data = data;
	}
	
	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
	    StringBuilder sb = new StringBuilder();
	    sb.append("{").append(getData().toString()).append(",[");
	    int i = 0;
	    for (Node<T> e : getChildren()) {
	        if (i > 0) {
	            sb.append(",");
	        }
	        sb.append(e.getData().toString());
	        i++;
	    }
	    sb.append("]").append("}");
	    return sb.toString();
	}

	/*
	 * (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#getId()
	 */
	@Override
	public String getId() {
		return _id;
	}

	/*
	 * (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#setId(java.lang.String)
	 */
	@Override
	public void setId(String id) {
		_id = id;
	}

	/*
	 * (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#getParentId()
	 */
	@Override
	public String getParentId() {
		return _parentId;
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#setParentId(java.lang.String)
	 */
	@Override
	public void setParentId(String parentId) {
		_parentId = parentId;
	}

	/*
	 * (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#getChild(java.lang.String)
	 */
	@Override
	public Node<T> getChild(String id) {
		if(_children != null) {
			for (int i = 0; i < _children.size(); i++) { // Should not use an Iterator here because we will scan the list many times
				Node<T> node = _children.get(i);
				if(Objects.equal(node.getId(), id)) {
					return node;
				} else if(node.hasChildren()){
					Node<T> child = node.getChild(id);
					if(child != null) {
						return child;
					}
				}				
			}
		}
		return null;
	}

	/*
	 * (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#hasChildren()
	 */
	@Override
	public boolean hasChildren() {
		return (!getChildren().isEmpty());
	}

	/* (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#hasConstrains()
	 */
	@Override
	public boolean hasConstrains() {
		return !_constrains.isEmpty();
	}
	
	/* (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#mustDisplay()
	 */
	@Override
	public boolean mustDisplay() {
		if(isDisplayIfEmpty()){
			return Boolean.TRUE;
		}
		return (getData() != null && !getData().isEmpty()); // We don't display the Node is the Data is empty
	}

	/* (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#getConstrains()
	 */
	@Override
	public List<NodeConstrain> getConstrains() {
		return ImmutableList.copyOf(_constrains);
	}
	
	@Override
	public void addAllConstrains(List<NodeConstrain> constrains) {
		_constrains.addAll(constrains);
	}

	/* (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#addConstrain(org.libermundi.theorcs.core.model.NodeConstrain)
	 */
	@Override
	public void addConstrain(NodeConstrain constrain) {
		_constrains.add(constrain);		
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((_id == null) ? 0 : _id.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		NodeImpl<?> other = (NodeImpl<?>) obj;
		if (_id == null) {
			if (other._id != null) {
				return false;
			}
		} else if (!_id.equals(other._id)) {
			return false;
		}
		return true;
	}
	
	@Override
	public Node<T> clone() throws CloneNotSupportedException {
		Node<T> clone = new NodeImpl<>(this._id, this._data);
		clone.setDisplayIfEmpty(isDisplayIfEmpty());
		clone.setParentId(getParentId());
		for (Node<T> child : getChildren()) {
			clone.getChildren().add(child.clone());
		}
		clone.addAllConstrains(getConstrains());
		return clone;
	}

	/* (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#setConstrainMode(org.libermundi.theorcs.core.model.NodeConstrain.Mode)
	 */
	@Override
	public void setConstrainMode(Mode mode) {
		_constrainMode= mode;		
	}

	/* (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#getConstrainMode()
	 */
	@Override
	public Mode getConstrainMode() {
		return _constrainMode;
	}

	/* (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#setDisplayIfEmpty(boolean)
	 */
	@Override
	public void setDisplayIfEmpty(boolean displayIfEmpty) {
		_displayIfEmpty = displayIfEmpty;
	}

	/* (non-Javadoc)
	 * @see org.libermundi.theorcs.core.model.Node#isDisplayIfEmpty()
	 */
	@Override
	public boolean isDisplayIfEmpty() {
		return _displayIfEmpty;
	}
	
	
}
